package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private CategoryFeignClient categoryFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private AlbumIndexRepository albumIndexRepository;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    public void upperAlbum(Long albumId) {
        //  上架的时候，使用多线程，提高查询效率.
        //  本质：给 AlbumInfoIndex 赋值
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //  CompletableFuture.runAsync(); // 无返回值
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //  赋值：albumInfo;
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            //  判断
            Assert.notNull(albumInfoResult, "专辑结果集不存在");
            AlbumInfo albumInfo = albumInfoResult.getData();
            //  判断
            Assert.notNull(albumInfo, "专辑不存在");
            //  属性拷贝：
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            //  返回数据
            return albumInfo;
        }, threadPoolExecutor);// 有返回值

        //  获取用户信息
        CompletableFuture<Void> userCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            Result<UserInfoVo> infoVoResult = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            // 判断
            Assert.notNull(infoVoResult, "用户信息结果集不存在");
            UserInfoVo userInfoVo = infoVoResult.getData();
            Assert.notNull(userInfoVo, "用户信息不存在");
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolExecutor);

        //  获取分类信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //  远程调用
            Result<BaseCategoryView> categoryViewResult = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            Assert.notNull(categoryViewResult, "分类结果集不存在");
            BaseCategoryView baseCategoryView = categoryViewResult.getData();
            Assert.notNull(baseCategoryView, "分类不存在");
            //  赋值数据
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
            //  albumInfoIndex.setCategory3Id(baseCategoryView.getCategory3Id());
        }, threadPoolExecutor);

        //  获取专辑对应的属性与属性值数据
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            //  远程调用
            Result<List<AlbumAttributeValue>> result = albumInfoFeignClient.findAlbumAttributeValue(albumId);
            Assert.notNull(result, "专辑属性值结果集不存在");
            List<AlbumAttributeValue> albumAttributeValueList = result.getData();
            Assert.notNull(albumAttributeValueList, "专辑属性值不存在");
            //  循环遍历这个集合数据给 AttributeValueIndex 中的属性赋值！ 最终得到一个新的集合，这个集合的泛型AtrributeValueIndex！
            List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream().map(albumAttributeValue -> {
                //  创建对象 AtrributeValueIndex
                AttributeValueIndex atrributeValueIndex = new AttributeValueIndex();
                //  属性拷贝：
                BeanUtils.copyProperties(albumAttributeValue, atrributeValueIndex);
                return atrributeValueIndex;
            }).collect(Collectors.toList());
            /*
            @Field(type = FieldType.Nested)
            private List<AttributeValueIndex> attributeValueIndexList;
             */
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
        }, threadPoolExecutor);

        //  给当前专辑对应的统计数据赋值：播放量，订阅量，购买量，评论量
        /*
        select stat.album_id,
               max(if(stat.stat_type='0401',stat.stat_num,0)) playStatNum,
               max(if(stat.stat_type='0402',stat.stat_num,0)) subscribeStatNum,
               max(if(stat.stat_type='0403',stat.stat_num,0)) buyStatNum,
               max(if(stat.stat_type='0404',stat.stat_num,0)) commentStatNum
        from album_stat stat
        where album_id = ?
        group by stat.album_id;
         */
        int playStatNum = new Random().nextInt(100000);
        int subscribeStatNum = new Random().nextInt(1000);
        int buyStatNum = new Random().nextInt(10000000);
        int commentStatNum = new Random().nextInt(10000);
        albumInfoIndex.setPlayStatNum(playStatNum);
        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
        albumInfoIndex.setBuyStatNum(buyStatNum);
        albumInfoIndex.setCommentStatNum(commentStatNum);
        //  远程调用获取数据完成之后，要做多任务组合
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                userCompletableFuture,
                categoryCompletableFuture,
                attrCompletableFuture
        ).join();

        //  保存：albumInfoIndex 包 es 中！ 1. es 中必须有索引库！
        albumIndexRepository.save(albumInfoIndex);

        //  保存专辑提词库数据. suggestinfo
        //  titile = 三国演义;
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        //  三国演义
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        //  sanguoyanyi
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        //  sgyy
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        this.suggestIndexRepository.save(suggestIndex);

        //  intro = 三国演义;
        SuggestIndex suggestIndexIntro = new SuggestIndex();
        suggestIndexIntro.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        //  三国演义
        suggestIndexIntro.setTitle(albumInfoIndex.getAlbumIntro());
        suggestIndexIntro.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumIntro()}));
        //  sanguoyanyi
        suggestIndexIntro.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro())}));
        //  sgyy
        suggestIndexIntro.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumIntro())}));
        this.suggestIndexRepository.save(suggestIndexIntro);
    }

    @Override
    public List<String> completeSuggest(String keyword) {
        //  创建一个集合
        List<String> list = new ArrayList<>();
        //  在 es 中是如何实现的?
        SearchResponse<SuggestIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(s -> s.index("suggestinfo")
                            .suggest(sg -> sg.suggesters("keyword-suggest"
                                            ,
                                            ss -> ss.completion(
                                                    c -> c.field("keyword").skipDuplicates(true).size(10).fuzzy(f -> f.fuzziness("auto"))
                                            ).prefix(keyword))
                                    .suggesters("py-suggest"
                                            ,
                                            ss -> ss.completion(
                                                    c -> c.field("keywordPinyin").skipDuplicates(true).size(10).fuzzy(f -> f.fuzziness("auto"))
                                            ).prefix(keyword))
                                    .suggesters("se-suggest"
                                            ,
                                            ss -> ss.completion(
                                                    c -> c.field("keywordSequence").skipDuplicates(true).size(10).fuzzy(f -> f.fuzziness("auto"))
                                            ).prefix(keyword))
                            )
                    ,
                    SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  从结果集中获取数据：
        List<String> s1 = this.getSuggestResult(searchResponse, "keyword-suggest");
        List<String> s2 = this.getSuggestResult(searchResponse, "py-suggest");
        List<String> s3 = this.getSuggestResult(searchResponse, "se-suggest");
        //  将s1,s2,s3三个集合汇总数据！
        list.addAll(s1);
        list.addAll(s2);
        list.addAll(s3);
        //  list 集合数据是否够10条记录? 再查询albuminfo 索引库！

        //  返回数据
        return list;
    }

    private List<String> getSuggestResult(SearchResponse<SuggestIndex> searchResponse, String key) {
        //  创建一个集合
        List<String> stringList = new ArrayList<>();
        //  根据key获取到集合数据
        List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(key);
        //  判断这个集合是否为空
        if (!CollectionUtils.isEmpty(suggestionList)) {
            //  循环遍历
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : suggestionList) {
                stringList = suggestIndexSuggestion.completion().options().stream().map(option -> {
                    //  获取数据并返回
                    String title = option.source().getTitle();
                    //  返回数据
                    return title;
                }).collect(Collectors.toList());
            }
        }
        return stringList;
    }

    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //  1.  一级分类下对应有多少个三级分类数据.
        Result<List<BaseCategory3>> listResult = categoryFeignClient.findTopBaseCategory3(category1Id);
        Assert.notNull(listResult, "一级分类下对应三级分类数据不存在");
        List<BaseCategory3> baseCategory3List = listResult.getData();
        Assert.notNull(baseCategory3List, "一级分类下对应三级分类数据集合不存在");
        //  baseCategory3List 将这个集合转换为map 集合 key=category3Id value=baseCategory3;
        Map<Long, BaseCategory3> baseCategory3Map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
        //  先获取到三级分类Id集合
        List<Long> category3IdList = baseCategory3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());
        //  Long 数据类型 转换为elasticsearch的FieldValue类型
        List<FieldValue> category3IdFieldValueList = category3IdList.stream().map(category3Id -> FieldValue.of(category3Id)).collect(Collectors.toList());
        //  # 一级分类Id=1 热点数据 三级分类Id 1001 - 1007
        //  ts.value() 需要一个 List<FieldValue> 集合；这个集合的本质数据 三级分类Id集合列表
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(s -> s.index("albuminfo")
                            .query(q -> q.terms(t -> t.field("category3Id").terms(ts -> ts.value(category3IdFieldValueList))))
                            .aggregations("category3IdAgg", a -> a.terms(t -> t.field("category3Id").size(10))
                                    .aggregations("topSixAgg", ag -> ag.topHits(h -> h.size(6).sort(st -> st.field(f -> f.field("playStatNum").order(SortOrder.Desc)))))
                            )
                    , AlbumInfoIndex.class);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  List<Map<String, Object>> 中要存储 三级分类对象 与 专辑的集合列表
        //  分析map 集合数据从哪里能获取到? 从聚合中获取数据！
        Aggregate category3IdAgg = searchResponse.aggregations().get("category3IdAgg");
        //  这个 地方是否可以使用 map?
        List<Map<String, Object>> list = category3IdAgg.lterms().buckets().array().stream().map(bucket -> {
            //  创建 map 集合
            Map<String, Object> map = new HashMap<>();
            //  获取到了三级分类Id
            long category3Id = bucket.key();
            //  获取专辑数据集合
            List<AlbumInfoIndex> albumInfoIndexList = bucket.aggregations().get("topSixAgg").topHits().hits().hits().stream().map(hit -> {
                String dataJson = hit.source().toString();
                AlbumInfoIndex albumInfoIndex = JSON.parseObject(dataJson, AlbumInfoIndex.class);
                return albumInfoIndex;
            }).collect(Collectors.toList());
            //  获取到了三级分类对象
            map.put("baseCategory3", baseCategory3Map.get(category3Id));
            map.put("list", albumInfoIndexList);
            //  返回数据集合
            return map;
        }).collect(Collectors.toList());
        //  返回集合数据
        return list;
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //  1.  先利用java 代码生成 searchRequest
        SearchRequest searchRequest = this.buildDsl(albumIndexQuery);

        //  dsl 语句:
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  构建返回数据
        AlbumSearchResponseVo albumSearchResponseVo = this.parseResultData(searchResponse);
       /*
        本质：给这个对象的属性赋值：
            private List<AlbumInfoIndexVo> list = new ArrayList<>(); 所有的专辑集合列表
            private Long total;//总记录数
            private Integer pageSize;//每页显示的内容
            private Integer pageNo;//当前页面
            private Long totalPages;//总页数
         */
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        //  10 3 4  9 3 3
        //  已知数据总数，以及每页显示的条数，计算总页数
        //  long totalPages = albumSearchResponseVo.getTotal()%albumSearchResponseVo.getPageSize()==0?albumSearchResponseVo.getTotal()/albumSearchResponseVo.getPageSize():(albumSearchResponseVo.getTotal()/albumSearchResponseVo.getPageSize()+1);
        long totalPages = (albumSearchResponseVo.getTotal() + albumSearchResponseVo.getPageSize() - 1) / albumSearchResponseVo.getPageSize();
        //  赋值总页数
        albumSearchResponseVo.setTotalPages(totalPages);
        //  返回对象
        return albumSearchResponseVo;
    }

    /**
     * 返回数据
     *
     * @param searchResponse
     * @return
     */
    private AlbumSearchResponseVo parseResultData(SearchResponse<AlbumInfoIndex> searchResponse) {
        //  给对象中的 private List<AlbumInfoIndexVo> list = new ArrayList<>(); 属性赋值：
        AlbumSearchResponseVo searchResponseVo = new AlbumSearchResponseVo();
        //  获取到 hits.hits._source 这里的数据.
        List<AlbumInfoIndexVo> list = searchResponse.hits().hits().stream().map(hit -> {
            //  创建数据载体对象
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            AlbumInfoIndex albumInfoIndex = hit.source();
            //  属性拷贝：
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            //  判断是否有高亮数据：
            if (null != hit.highlight().get("albumTitle")) {
                //  获取高亮字段并赋值
                String albumTitle = hit.highlight().get("albumTitle").get(0);
                albumInfoIndexVo.setAlbumTitle(albumTitle);
            }
            //  返回对象数据
            return albumInfoIndexVo;
        }).collect(Collectors.toList());
        //  赋值
        searchResponseVo.setList(list);
        //  返回数据
        return searchResponseVo;
    }

    /**
     * 构建DSL语句
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildDsl(AlbumIndexQuery albumIndexQuery) {
        //  创建SearchRequest 对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        //  创建一个 Query 对象 query
        Query.Builder queryBuilder = new Query.Builder();
        //  创建内部 bool
        BoolQuery.Builder subBoolQueryBuilder = new BoolQuery.Builder();
        //  直接指定索引库的名词：只需要指定一次！
        searchRequestBuilder.index("albuminfo");
        //  1.  先判断用户是否通过关键词检修检索.
        if (!StringUtils.isEmpty(albumIndexQuery.getKeyword())) {
            //  query--bool--should
            //  指定索引库. 核心思想：一个Function 代表一个对象！+ 拉姆达表达式
            subBoolQueryBuilder.should(s -> s.match(m -> m.field("albumTitle").query(albumIndexQuery.getKeyword())))
                    .should(s -> s.match(m -> m.field("albumIntro").query(albumIndexQuery.getKeyword())))
                    .should(s -> s.match(m -> m.field("announcerName").query(albumIndexQuery.getKeyword())))
            ;
            //  设置高亮字段
            searchRequestBuilder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<span style=color:#f86442>").postTags("</span>")));
        }
        //  判断用户是否根据分类Id 检索
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory1Id())) {
            subBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory2Id())) {
            subBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory3Id())) {
            subBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        //  从两个入口进来之后，可能根据属性与属性值进行检索. 数据类型特殊：nested!
        //  先判断是否根据属性与属性值进行过滤. 前端传递：属性id:属性值id
        List<String> attributeList = albumIndexQuery.getAttributeList();
        //  判断集合是否为空
        if (!CollectionUtils.isEmpty(attributeList)) {
            //  循环遍历.
            for (String attr : attributeList) {
                //  使用：分割
                String[] split = attr.split(":");
                //  判断
                if (null != split && split.length == 2) {
                    //  嵌套的bool
                    subBoolQueryBuilder.filter(f->f.nested(n->n.path("attributeValueIndexList")
                            .query(q->q.bool(b1->b1.must(m->m.term(t->t.field("attributeValueIndexList.attributeId").value(split[0])))
                                    .must(m->m.term(tv->tv.field("attributeValueIndexList.valueId").value(split[1])))
                            ))
                    ));
                }
            }
        }
        //  提取查询方法.
        //  query-->bool
        queryBuilder.bool(subBoolQueryBuilder.build());
        searchRequestBuilder.query(queryBuilder.build());
        //  分页：
        //  10; 0,3 3,3 6,3
        int from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(albumIndexQuery.getPageSize());
        //  排序: 综合排序 hotScore [1:desc] 播放量playStatNum [2:desc] 发布时间createTime[3:desc]；asc:升序 desc:降序）
        //  order=2:desc 按照播放量，降序排列
        String order = albumIndexQuery.getOrder();
        //  判断
        if (!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            if (null != split && split.length == 2) {
                //  声明一个排序字段变量
                String filed = "";
                switch (split[0]) {
                    case "1":
                        filed = "hotScore";
                        break;
                    case "2":
                        filed = "playStatNum";
                        break;
                    case "3":
                        filed = "createTime";
                        break;
                }
                //  设置排序规则
                String finalFiled = filed;
                searchRequestBuilder.sort(s -> s.field(f -> f.field(finalFiled).order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
            }
        }


        //  构建searchRequest 对象 本质是构建DSL语句的！
        SearchRequest searchRequest = searchRequestBuilder.build();
        //  想把DSL语句打印出来.
        log.info("DSL语句：{}", searchRequest.toString());
        System.out.println("dsl:\t" + searchRequest.toString());
        //  返回对象
        return searchRequest;
    }

    @Override
    public void lowerAlbum(Long albumId) {
        //  删除：
        albumIndexRepository.deleteById(albumId);
    }
}
